package com.tripcube.system.service.impl;

import com.tripcube.common.redis.key.UserProviderCacheKeyBuilder;
import com.tripcube.system.domain.SysUserCheckins;
import com.tripcube.system.domain.dto.TravelCheckinDTO;
import com.tripcube.system.domain.vo.CheckinVO;
import com.tripcube.system.mapper.SysUserCheckinsMapper;
import com.tripcube.system.service.SysUserCheckinsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.YearMonth;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author alander
 * @description 用户打卡记录表Service实现
 * @createDate 2025-09-24 21:45:13
 */
@Service
public class SysUserCheckinsServiceImpl implements SysUserCheckinsService {

    @Autowired
    private SysUserCheckinsMapper checkinsMapper;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private UserProviderCacheKeyBuilder userProviderCacheKeyBuilder;

    /**
     * 根据条件分页查询打卡记录列表
     *
     * @param checkins 打卡记录信息
     * @return 打卡记录集合信息
     */
    @Override
    public List<SysUserCheckins> selectCheckinsList(SysUserCheckins checkins) {
        return checkinsMapper.selectCheckinsList(checkins);
    }

    /**
     * 通过打卡记录ID查询打卡记录
     *
     * @param id 打卡记录ID
     * @return 打卡记录对象信息
     */
    @Override
    public SysUserCheckins selectCheckinsById(Long id) {
        return checkinsMapper.selectCheckinsById(id);
    }

    /**
     * 新增打卡记录
     *
     * @param checkins 打卡记录信息
     * @return 结果
     */
    @Override
    public int insertCheckins(SysUserCheckins checkins) {
        return checkinsMapper.insertCheckins(checkins);
    }

    /**
     * 修改打卡记录
     *
     * @param checkins 打卡记录信息
     * @return 结果
     */
    @Override
    public int updateCheckins(SysUserCheckins checkins) {
        return checkinsMapper.updateCheckins(checkins);
    }

    /**
     * 批量删除打卡记录信息
     *
     * @param ids 需要删除的打卡记录ID
     * @return 结果
     */
    @Override
    public int deleteCheckinsByIds(Long[] ids) {
        return checkinsMapper.deleteCheckinsByIds(ids);
    }

    /**
     * 删除打卡记录信息
     *
     * @param id 打卡记录ID
     * @return 结果
     */
    @Override
    public int deleteCheckinsById(Long id) {
        return checkinsMapper.deleteCheckinsById(id);
    }

    /**
     * 用户签到 
     * @param dto 签到信息
     */
    @Override
    public Long checkin(TravelCheckinDTO dto) {
        Long userId = dto.getUserId();
        LocalDateTime today = LocalDateTime.now();

        // 检查今日是否已签到
        if (dto.getCheckinType() == 1) {
            if (isCheckedInBitmap(userId, LocalDate.from(today))) {
                throw new RuntimeException("今日已签到");
            }
        }

        // 计算积分
        int points = calculatePoints(dto);

        // 构建打卡记录
        SysUserCheckins checkin = buildCheckin(dto, points, today);

        // Redis操作
        if (dto.getCheckinType() == 1){
            updateRedisAfterCheckinBitmap(userId, LocalDate.from(today), points);
        }

        // 异步写入数据库
        asyncSaveCheckin(checkin);

        return checkin.getId();
    }

    /**
     * 检查用户今日是否已签到 
     * @param userId 用户ID
     * @return 是否已签到
     */
    @Override
    public boolean checkinToday(Long userId) {
        return isCheckedInBitmap(userId, LocalDate.now());
    }

    @Override
    public Long checkinDays(Long userId) {
        String daysKey = userProviderCacheKeyBuilder.buildCheckinDaysKey(userId);
        String days = redisTemplate.opsForValue().get(daysKey);
        if (days != null) {
            return Long.parseLong(days);
        }
        Long count = checkinsMapper.checkinDays(userId);
        redisTemplate.opsForValue().set(daysKey, String.valueOf(count), 30, TimeUnit.DAYS);
        return count;
    }


    /**
     * 获取用户连续签到天数 
     * @param userId 用户ID
     * @return 连续签到天数
     */
    @Override
    public Long continuousDays(Long userId) {
        String continuousKey = userProviderCacheKeyBuilder.buildContinuousDaysKey(userId);
        String days = redisTemplate.opsForValue().get(continuousKey);

        if (days != null) {
            return Long.parseLong(days);
        }
        return calculateContinuousDaysBitmap(userId);
    }


    @Override
    public Long travelCheckinCount(Long userId) {
        String travelCountKey = userProviderCacheKeyBuilder.buildTravelCountKey(userId);
        String redisCount = redisTemplate.opsForValue().get(travelCountKey);
        if (redisCount != null) {
            return Long.parseLong(redisCount);
        }

        Long count = checkinsMapper.travelCheckinCount(userId);
        redisTemplate.opsForValue().set(travelCountKey, String.valueOf(count), 30, TimeUnit.DAYS);
        return count;
    }



    /**
     * 旅行地打卡
     * @param dto 打卡信息
     * @return 打卡ID
     */
    @Override
    public Long travelCheckin(TravelCheckinDTO dto) {
        dto.setCheckinType(2);
        return checkin(dto);
    }

    /**
     * 获取附近打卡地点
     * @param lat 纬度
     * @param lng 经度
     * @param meter 距离范围(米)
     * @return 打卡地点列表
     */
    @Override
    public List<CheckinVO> nearby(BigDecimal lat, BigDecimal lng, int meter) {
        return checkinsMapper.selectNearbyCheckins(lat, lng, meter);
    }

    /**
     * 分页查询用户打卡记录
     * @param userId 用户ID
     * @return 打卡记录分页数据
     */
    @Override
    public List<SysUserCheckins> pageByUser(Long userId) {
        // 查询指定用户的打卡记录
        SysUserCheckins checkinsQuery = new SysUserCheckins();
        checkinsQuery.setUserId(userId);
        List<SysUserCheckins> checkins = checkinsMapper.selectCheckinsList(checkinsQuery);
        if(!checkins.isEmpty()){
            return checkins;
        }else {
            throw new RuntimeException("无打卡记录");
        }
    }

    /**
     * 获取用户上月获得的积分总和
     * @param userId 用户ID
     * @return 积分总和
     */
    @Override
    public int sumPointsLastMonth(Long userId) {
        LocalDate now = LocalDate.now();
        YearMonth lastMonth = YearMonth.now().minusMonths(1);
        String monthlyPointsKey = userProviderCacheKeyBuilder.buildMonthlyPointsKey(userId, lastMonth);

        String points = redisTemplate.opsForValue().get(monthlyPointsKey);
        if (points != null) {
            return Integer.parseInt(points);
        }

        // Redis无数据时查询数据库
        return calculateMonthlyPointsDB(userId, lastMonth);
    }


    /**
     * 撤销打卡记录
     * @param checkinId 打卡记录ID
     */
    @Override
    public void revoke(Long checkinId) {
        SysUserCheckins checkin = new SysUserCheckins();
        checkin.setId(checkinId);
        checkin.setStatus("1");
        checkinsMapper.updateCheckins(checkin);
    }

    /**
     * 恢复已撤销的打卡记录
     * @param checkinId 打卡记录ID
     */
    @Override
    public void restore(Long checkinId) {
        SysUserCheckins checkin = new SysUserCheckins();
        checkin.setId(checkinId);
        checkin.setStatus("0");
        checkinsMapper.updateCheckins(checkin);
    }

    /**
     * 计算打卡积分
     * @param dto 打卡信息
     * @return 获得的积分
     */
    private int calculatePoints(TravelCheckinDTO dto) {
        int points = 0;

        // 每日签到基础积分
        if (dto.getCheckinType() == 1) { // 每日签到
            points = 10;
        } else if (dto.getCheckinType() == 2) { // 旅行地打卡
            points = 20;

            // 如果有照片额外加分
            if (dto.getPhotos() != null && dto.getPhotos().length > 0) {
                points += Math.min(dto.getPhotos().length, 5) * 2; // 每张照片最多加2分，最多加10分
            }

            // 如果有描述额外加分
            if (dto.getDescription() != null && dto.getDescription().length() > 10) {
                points += 5; // 描述超过10个字符加5分
            }
        }

        return points;
    }

    // =============== Bitmap辅助方法 ===============
    /**
     * 检查用户某日是否已签到 
     */
    private boolean isCheckedInBitmap(Long userId, LocalDate date) {
        String key = userProviderCacheKeyBuilder.buildCheckinBitmapKey(userId);
        int offset = calculateDayOffset(date);
        return Boolean.TRUE.equals(redisTemplate.opsForValue().getBit(key, offset));
    }

    /**
     * 计算连续签到天数 
     */
    private Long calculateContinuousDaysBitmap(Long userId) {
        String continuousKey = userProviderCacheKeyBuilder.buildContinuousDaysKey(userId);
        String days = redisTemplate.opsForValue().get(continuousKey);

        if (days != null) {
            return Long.parseLong(days);
        }

        // 从Bitmap计算连续天数
        String key = userProviderCacheKeyBuilder.buildCheckinBitmapKey(userId);
        Long continuousDays = 0L;
        LocalDate checkDate = LocalDate.now();

        // 从今天开始向前检查连续签到
        while (true) {
            int offset = calculateDayOffset(checkDate);
            Boolean isChecked = redisTemplate.opsForValue().getBit(key, offset);

            if (Boolean.TRUE.equals(isChecked)) {
                continuousDays++;
                checkDate = checkDate.minusDays(1);
            } else {
                break;
            }
        }

        // 缓存连续签到天数
        redisTemplate.opsForValue().set(continuousKey, String.valueOf(continuousDays), 30, TimeUnit.DAYS);
        return continuousDays;
    }


    /**
     * 更新Redis签到信息 
     */
    private void updateRedisAfterCheckinBitmap(Long userId, LocalDate today, int points) {
        String bitmapKey = userProviderCacheKeyBuilder.buildCheckinBitmapKey(userId);
        int offset = calculateDayOffset(today);

        // 设置签到位
        redisTemplate.opsForValue().setBit(bitmapKey, offset, true);
        // 设置Bitmap过期时间（保留两年数据）
        redisTemplate.expire(bitmapKey, 730, TimeUnit.DAYS);

        // 更新连续签到天数
        String continuousKey = userProviderCacheKeyBuilder.buildContinuousDaysKey(userId);
        Long continuousDays = calculateContinuousDaysBitmap(userId);
        redisTemplate.opsForValue().set(continuousKey, String.valueOf(continuousDays), 30, TimeUnit.DAYS);

        // 更新月度积分
        String monthlyPointsKey = userProviderCacheKeyBuilder.buildMonthlyPointsKey(userId, YearMonth.from(today));
        redisTemplate.opsForValue().increment(monthlyPointsKey, points);
        redisTemplate.expire(monthlyPointsKey, 30, TimeUnit.DAYS);
    }

    /**
     * 计算日期偏移量（相对于当年1月1日）
     */
    private int calculateDayOffset(LocalDate date) {
        LocalDate startOfYear = date.withDayOfYear(1);
        return (int) ChronoUnit.DAYS.between(startOfYear, date);
    }

    /**
     * 计算某月签到次数 
     */
    public long countMonthlyCheckins(Long userId, YearMonth month) {
        // 简化实现，返回0
        return 0;
    }

    // =============== 数据库兜底方法 ===============
    @Async
    public void asyncSaveCheckin(SysUserCheckins checkin) {
        try {
            checkinsMapper.insertCheckins(checkin);
        } catch (Exception e) {
            // 记录日志
            e.printStackTrace();
        }
    }

    private int calculateContinuousDaysDB(Long userId) {
        // 查询未被撤销的打卡记录，按日期倒序排列
        SysUserCheckins checkinsQuery = new SysUserCheckins();
        checkinsQuery.setUserId(userId);
        checkinsQuery.setDelFlag("0");
        List<SysUserCheckins> checkins = checkinsMapper.selectCheckinsList(checkinsQuery);

        // 按日期倒序排序
        checkins.sort((c1, c2) -> c2.getCheckinDate().compareTo(c1.getCheckinDate()));

        if (checkins.isEmpty()) {
            return 0;
        }

        int continuousDays = 0;
        LocalDate expectedDate = LocalDate.now();

        // 从最近的打卡日期开始向前计算连续天数
        for (SysUserCheckins checkin : checkins) {
            LocalDate checkinDate = checkin.getCheckinDate().toLocalDate();

            // 如果打卡日期等于期望日期，则连续天数加1
            if (checkinDate.equals(expectedDate)) {
                continuousDays++;
                expectedDate = expectedDate.minusDays(1); // 期望的下一个日期
            } else if (checkinDate.isBefore(expectedDate)) {
                break;
            }
        }

        return continuousDays;
    }

    private int calculateMonthlyPointsDB(Long userId, YearMonth month) {
        // 计算上个月的开始和结束日期
        LocalDate startDate = month.atDay(1);
        LocalDate endDate = month.atEndOfMonth();

        SysUserCheckins checkinsQuery = new SysUserCheckins();
        checkinsQuery.setUserId(userId);
        List<SysUserCheckins> checkins = checkinsMapper.selectCheckinsList(checkinsQuery);

        return checkins.stream()
                .filter(checkin -> !checkin.getDelFlag().equals("2")) // 过滤已删除的记录
                .filter(checkin ->
                        (checkin.getCheckinDate().toLocalDate().isEqual(startDate) || checkin.getCheckinDate().toLocalDate().isAfter(startDate)) &&
                                (checkin.getCheckinDate().toLocalDate().isEqual(endDate) || checkin.getCheckinDate().toLocalDate().isBefore(endDate)))
                .mapToInt(SysUserCheckins::getPointsEarned)
                .sum();
    }

    private SysUserCheckins buildCheckin(TravelCheckinDTO dto, int points, LocalDateTime today) {
        SysUserCheckins checkin = new SysUserCheckins();
        checkin.setUserId(dto.getUserId());
        checkin.setCheckinType(dto.getCheckinType());
        checkin.setLocationName(dto.getLocationName());
        checkin.setLatitude(dto.getLatitude());
        checkin.setLongitude(dto.getLongitude());
        checkin.setAddress(dto.getAddress());
        // 将照片数组转换为JSON字符串存储
        if (dto.getPhotos() != null) {
            // 转换为JSON数组格式
            StringBuilder jsonPhotos = new StringBuilder("[");
            for (int i = 0; i < dto.getPhotos().length; i++) {
                if (i > 0) {
                    jsonPhotos.append(",");
                }
                jsonPhotos.append("\"").append(dto.getPhotos()[i]).append("\"");
            }
            jsonPhotos.append("]");
            checkin.setPhotos(jsonPhotos.toString());
        }
        checkin.setDescription(dto.getDescription());
        checkin.setPointsEarned(points); // 使用计算后的积分
        checkin.setCheckinDate(today);
        checkin.setCreateTime(today);
        checkin.setDelFlag("0");
        checkin.setStatus("0");
        return checkin;
    }
}